/*
 * PhaseBuilder.cpp
 *
 *  Created on: 14 Jul 2011
 *      Author: al2010
 */

#include "PhaseBuilder.h"

// C++ includes
#include <algorithm>
#include <set>

// Boost includes
#include <boost/foreach.hpp>

//GeoReact includes
#include "Database.h"
#include "Phase.h"
#include "PhaseSystem.h"
#include "SpeciesMap.h"

#include "AqueousPhase.h"
#include "GaseousPhase.h"
#include "MineralPhase.h"

const PhaseSystem PhaseBuilder::CreatePhaseSystem(const vector<string>& allSpecies)
{
	return PhaseSystem( GeneratePhases(allSpecies) );
}

const vector<Phase> PhaseBuilder::GeneratePhases(const vector<string>& allSpecies)
{
	vector<Phase> phases;
	
	vector<string> aqueousSpecies = FilterSpecies(allSpecies, {"aqueous"});
	vector<string> gaseousSpecies = FilterSpecies(allSpecies, {"gaseous"});
	vector<string> mineralSpecies = FilterSpecies(allSpecies, {"mineral"});
	
	if(!aqueousSpecies.empty())
		phases.push_back( CreateGenericPhase( AqueousPhase(aqueousSpecies), "Aqueous" ) );
	
	if(!gaseousSpecies.empty())
		phases.push_back( CreateGenericPhase( GaseousPhase(gaseousSpecies), "Gaseous" ) );
	
	BOOST_FOREACH(const string& mineral, mineralSpecies)
		phases.push_back( CreateGenericPhase( MineralPhase({mineral}), "Mineral" ) );
	
	return phases;
}

const vector<string> PhaseBuilder::GenerateSpecies(const vector<string>& knownSpecies)
{
	const vector<string> basisSpecies     = GenerateBasisSpecies(knownSpecies);
	const vector<string> secondarySpecies = GenerateSecondarySpecies(knownSpecies);
	
	vector<string> allSpecies;
	
	allSpecies.insert(allSpecies.end(), basisSpecies.begin(), basisSpecies.end());
	allSpecies.insert(allSpecies.end(), secondarySpecies.begin(), secondarySpecies.end());
	
	return allSpecies;
}

const vector<string> PhaseBuilder::GenerateSpeciesConsiderOnlySecondaryAqueous(const vector<string>& knownSpecies)
{
	const vector<string> basisSpecies = GenerateBasisSpecies(knownSpecies);
	
	const vector<string> secondarySpecies = GenerateSecondarySpecies(knownSpecies);
	
	const vector<string> aqueousSecondarySpecies = FilterSpecies(secondarySpecies, {"aqueous"});
	
	const vector<string> knownSecondarySpecies = RemoveSpecies(knownSpecies, {"basis"});
	
	set<string> allSpecies;
	
	allSpecies.insert(basisSpecies.begin(), basisSpecies.end());
	allSpecies.insert(aqueousSecondarySpecies.begin(), aqueousSecondarySpecies.end());
	allSpecies.insert(knownSecondarySpecies.begin(), knownSecondarySpecies.end());
	
	return vector<string>(allSpecies.begin(), allSpecies.end());
}

const vector<string> PhaseBuilder::GenerateBasisSpecies(const vector<string>& knownSpecies)
{
	// Get an instance of the database
	const Database& db = Database::GetInstance();
	
	// The set containing only unique basis species
	set<string> basisSpecies;
	
	// Make sure the hydrogen ion H[+] is present in the set of basis species in case H2O is known to exist
	if(std::find(knownSpecies.begin(), knownSpecies.end(), "H2O") != knownSpecies.end())
		basisSpecies.insert("H[+]");
	
	// Loop over all the known species
	BOOST_FOREACH(const string& species, knownSpecies)
	{
		// Check if the current known species is a basis species in the database
		if(db.GetSpeciesInfo(species).HasTag("basis"))
			basisSpecies.insert(species);
		else
		{
			// Get the info data of the reaction whose reactant is the current known species
			const ReactionInfo reactionInfo = db.GetReactionInfo(species);
		
			// Add all the products in the reaction to the set of basis species being created (all these products are basis species in the database)
			basisSpecies.insert(reactionInfo.products.begin(), reactionInfo.products.end());
		}
	}
	
	// Convert the set of basis species to a vector container and return it
	return vector<string>(basisSpecies.begin(), basisSpecies.end());
}

const vector<string> PhaseBuilder::GenerateSecondarySpecies(const vector<string>& knownSpecies)
{
	const vector<string> basisSpecies = GenerateBasisSpecies(knownSpecies);
	
	// Get an instance of the database
	const Database& db = Database::GetInstance();
	
	// Get a reference to the list of reactions in the database
	const Database::ReactionInfoList& reactionInfoList = db.GetReactionInfoList();
	
	// The vector of secondary species that can be built from the set of basis species
	vector<string> secondarySpecies;
	
	// Loop over all the reactions in the database
	BOOST_FOREACH(const ReactionInfo& r, reactionInfoList)
	{
		// An auxiliary boolean variable to determine if all the products of the current reaction belong to the set of basis species
		bool belong = true;
		
		//  Check if the products of the current reaction belong to the set of basis species 
		BOOST_FOREACH(const string& product, r.products)
			if(std::find(basisSpecies.begin(), basisSpecies.end(), product) == basisSpecies.end())
				belong = false;
		
		// Insert the reactant (a secondary species) of the current reaction into the vector of secondary species
		if(belong)
			secondarySpecies.push_back(r.reactant);
	}
	
	return secondarySpecies;
}

const vector<string> PhaseBuilder::FilterSpecies(const vector<string>& species, const vector<string>& tags)
{
	// The subset of the given species that have any of the given tags
	vector<string> newSpecies;
	
	// Loop over all the given species 
	BOOST_FOREACH(const string& s, species)
		if(Database::GetInstance().GetSpeciesInfo(s).HasAnyTag(tags))
			newSpecies.push_back(s);
			
	return newSpecies;
}

const vector<string> PhaseBuilder::RemoveSpecies(const vector<string>& species, const vector<string>& tags)
{
	// The subset of the given species that does not have any of the given tags
	vector<string> newSpecies;
	
	// Loop over all the given species 
	BOOST_FOREACH(const string& s, species)
		if(!Database::GetInstance().GetSpeciesInfo(s).HasAnyTag(tags))
			newSpecies.push_back(s);
			
	return newSpecies;
}
